link pagina dataset (https://www.kaggle.com/datasets/saurograndi/airplane-crashes-since-1908)
import numpy as np
np.set_printoptions(precision=2)
import plotly.express as px
import plotly.graph_objs as go
import pandas as pd
import calendar
df=pd.read_csv("data/Airplane_Crashes.csv")
#cn/ln --> Construction or serial number / Line or fuselage number
df
| Date | Time | Location | Operator | Flight # | Route | Type | Registration | cn/In | Aboard | Fatalities | Ground | Summary | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 09/17/1908 | 17:18 | Fort Myer, Virginia | Military - U.S. Army | NaN | Demonstration | Wright Flyer III | NaN | 1 | 2.0 | 1.0 | 0.0 | During a demonstration flight, a U.S. Army fly... |
| 1 | 07/12/1912 | 06:30 | AtlantiCity, New Jersey | Military - U.S. Navy | NaN | Test flight | Dirigible | NaN | NaN | 5.0 | 5.0 | 0.0 | First U.S. dirigible Akron exploded just offsh... |
| 2 | 08/06/1913 | NaN | Victoria, British Columbia, Canada | Private | - | NaN | Curtiss seaplane | NaN | NaN | 1.0 | 1.0 | 0.0 | The first fatal airplane accident in Canada oc... |
| 3 | 09/09/1913 | 18:30 | Over the North Sea | Military - German Navy | NaN | NaN | Zeppelin L-1 (airship) | NaN | NaN | 20.0 | 14.0 | 0.0 | The airship flew into a thunderstorm and encou... |
| 4 | 10/17/1913 | 10:30 | Near Johannisthal, Germany | Military - German Navy | NaN | NaN | Zeppelin L-2 (airship) | NaN | NaN | 30.0 | 30.0 | 0.0 | Hydrogen gas which was being vented was sucked... |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 5263 | 05/20/2009 | 06:30 | Near Madiun, Indonesia | Military - Indonesian Air Force | NaN | Jakarta - Maduin | Lockheed C-130 Hercules | A-1325 | 1982 | 112.0 | 98.0 | 2.0 | While on approach, the military transport cras... |
| 5264 | 05/26/2009 | NaN | Near Isiro, DemocratiRepubliCongo | Service Air | NaN | Goma - Isiro | Antonov An-26 | 9Q-CSA | 5005 | 4.0 | 4.0 | NaN | The cargo plane crashed while on approach to I... |
| 5265 | 06/01/2009 | 00:15 | AtlantiOcean, 570 miles northeast of Natal, Br... | Air France | 447 | Rio de Janeiro - Paris | Airbus A330-203 | F-GZCP | 660 | 228.0 | 228.0 | 0.0 | The Airbus went missing over the AtlantiOcean ... |
| 5266 | 06/07/2009 | 08:30 | Near Port Hope Simpson, Newfoundland, Canada | Strait Air | NaN | Lourdes de BlanSablon - Port Hope Simpson | Britten-Norman BN-2A-27 Islander | C-FJJR | 424 | 1.0 | 1.0 | 0.0 | The air ambulance crashed into hills while att... |
| 5267 | 06/08/2009 | NaN | State of Arunachal Pradesh, India | Military - Indian Air Force | NaN | Mechuka for Jorhat | Antonov An-32 | NaN | NaN | 13.0 | 13.0 | 0.0 | The military transport went missing while en r... |
5268 rows × 13 columns
Analisi per decidere che colonne prendere in considerazione e quindi decidere quanta importanza dargli.
dfc = pd.DataFrame({"column" : df.columns})
dfc["notna"] = dfc["column"].apply(lambda c: df.loc[df[c].notna(), c].count()*100/len(df.index))
dfc = dfc.sort_values(by='notna')
hc = px.histogram(dfc, orientation="h", y='column', x='notna', height= 500)
hc.layout["title"] = "Percentuale dati validi nel dataset per ogni colonna"
hc.layout["yaxis"]["title"] = "Colonne"
hc.layout["xaxis"]["title"] = "righe non NaN in percentuale"
hc.update_layout(bargap=0.5)
hc.show()
#Vengono divise le date per poter differenziare tra giorno, mese, anno
df["day"] = pd.DatetimeIndex(df["Date"]).day
df["month"] = pd.DatetimeIndex(df["Date"]).month
df["year"] = pd.DatetimeIndex(df["Date"]).year
df["total_fatalities"] = df["Fatalities"] + df["Ground"] #Calcolo delle morti totali causate dall'incidente
Domanda : Come si evolvono negli anni gli incidenti?
timeline_s = df.groupby("year")["year"].count()
timeline_crashes = px.line(timeline_s,
title=f"Numero di incidenti negli anni ({timeline_s.index.min()} - {timeline_s.index.max()})",
labels={"value": "Numero di incidenti", "index": "Anno"})
timeline_max = timeline_s.idxmax()
timeline_crashes.add_vline(x=timeline_max, line_width=1, line_color="red", annotation_text=f"{timeline_max} - {timeline_s.max()} incidenti")
timeline_crashes.update_layout(showlegend=False, font=dict(size= 16))
Risposta : Si può vedere che gli incidenti aumentano negli anni, perché sono sempre di più i velivoli e voli di linea che vengono effettuati. Dopo il picco nel 1972 si può notare che tendono a diminuire molto probabilmente perché sono state sviluppate negli anni nuove tecnologie e tecniche per poter evitare gli incidenti.
Domanda : Quale è la media mensile di incidenti del dataset, quale è il periodo dell'anno più critico?
dfByMonth = pd.DataFrame(pd.DataFrame(df.groupby(["year","month"]).size().reset_index(name='count')).groupby(["month"]).mean())
dfByMonth["month_name"] = dfByMonth.index
dfByMonth["month_name"] = dfByMonth["month_name"].apply(lambda x: calendar.month_name[x])
color = ['blue'] * 12
color[0] = 'darkblue'
color[11] = 'darkblue'
crashesDistributionByMonthH = go.Figure(data=[go.Bar(x=dfByMonth["month_name"], y=dfByMonth["count"], marker_color=color)])
crashesDistributionByMonthH.layout["title"] = "Numero di incidenti medi mensili (calcolato su tutti gli anni)"
crashesDistributionByMonthH.layout["xaxis"]["title"] = "Mese"
crashesDistributionByMonthH.layout["yaxis"]["title"] = "incidenti medi al mese"
crashesDistributionByMonthH.update_layout(height=500, bargap=0.5, font=dict(size= 16))
crashesDistributionByMonthH.show()
Risposta : Non ci sono valori molto sopra gli altri, ma comunque si può notare che Gennaio e Dicembre sono un po' più alti del normale. Questo potrebbe essere causato dal fatto che i dati potrebbero essere concentrati nell’emisfero Nord del pianeta. Quindi in quel periodo dell’anno la meteo è molto critica per gli aerei.
Domanda : Quali sono gli operatori meno affidabili?
gbOperator = df.groupby("Operator")
dfOperator = gbOperator.sum(["Aboard", "Fatalities", "Ground", "total_fatalities"]) #Per eventuali analisi future
dfOperator["n_crashes"] = gbOperator["Operator"].count()
dfOperator["mortality_aboard"] = dfOperator["Fatalities"]*100/dfOperator["Aboard"]
mostCrashesOperators = dfOperator.sort_values("n_crashes").tail(10)
crashesPercentage = mostCrashesOperators['n_crashes'].sum()*100/len(df)
topOperatorsMostCrashesH = px.histogram(mostCrashesOperators,
y = mostCrashesOperators.index,
x = "n_crashes",
title=f"I 10 Operatori con il maggior numero di incidenti (il {crashesPercentage:.1f}% di incidenti su {len(dfOperator)} operatori) ",
height = 600,
text_auto=True
)
topOperatorsMostCrashesH.layout["yaxis"]["title"] = "Operatore"
topOperatorsMostCrashesH.layout["xaxis"]["title"] = "Numero di incidenti"
topOperatorsMostCrashesH.update_layout(bargap=0.4, font=dict(size= 16))
topOperatorsMostCrashesH.show()
Risposta : Come si può vedere in cima alla classifica c’è l’operatore russo Aeroflot con subito dopo l’Air Force Statunitense.
Domanda : Si nota però che i militari americano appaiano già più volte nella classifica sottosezioni diverse dell’esercito. Quindi quanti sono veramente gli incidenti dei militari americani o altri tipi di militari?
dfOperator["military_type"] = "NON MILITARE"
dfOperator.loc[dfOperator.index.str.lower().str.contains("military"), "military_type"] = "ALTRI"
dfOperator.loc[dfOperator.index.str.lower().str.contains("military - u.s."), "military_type"] = "USA"
pieMilitaryType = px.pie(dfOperator, values='n_crashes', names='military_type', title = "Percentuale incidenti operatori militari rispetto agli altri")
pieMilitaryType.update_traces(textposition='inside', textinfo='label+percent')
pieMilitaryType.update_layout(showlegend=False,width=700,height=700, font=dict(size= 16))
pieMilitaryType.show()
Risposta : I militari americani hanno fatto 321 incidenti (rispetto ai 179 di Aeroflot). In totale gli incidenti di operatori militari sono il 15% del dataset.
Domanda : Quali sono i velivoli meno affidabili?
gbType = df.groupby("Type")
dfType = gbType.sum(["Aboard", "Fatalities", "Ground", "total_fatalities"]) #Per eventuali analisi future
dfType["n_crashes"] = gbType["Type"].count()
dfType["mortality_aboard"] = dfType["Fatalities"]*100/dfType["Aboard"]
mostCrashesTypes = dfType.sort_values("n_crashes").tail(10)
topTypesMostCrashesH = px.histogram(mostCrashesTypes,
y = mostCrashesTypes.index,
x = "n_crashes",
title="I 10 Velivoli con il maggior numero di incidenti",
height = 600,
text_auto=True
)
topTypesMostCrashesH.layout["yaxis"]["title"] = "Velivolo"
topTypesMostCrashesH.layout["xaxis"]["title"] = "Numero di incidenti"
topTypesMostCrashesH.update_layout(bargap=0.4, font=dict(size=16))
topTypesMostCrashesH.show()
Risposta : Come si può vedere in cima alla classifica spicca il Douglas DC-3, si potrebbe dire che è un velivolo poco affidabile. O forse potrebbe anche essere perché era uno dei velivoli più utilizzati e per questo ha avuto più incidenti rispetto agli altri.
Domanda : Comunque i velivoli di tipo Douglas appaiano spesso nella classifica. Quindi quanto spazio occupa nel numero d'incidenti rispetto ad altri importanti produttori di aerei?
#codice per capire possibli produttori con molti incidenti
possiblePlaneTypes = dict()
for t in df.loc[df["Type"].notna(), "Type"]:
for n in t.split():
name = n.lower()
if name in possiblePlaneTypes:
possiblePlaneTypes[name] += 1
else:
possiblePlaneTypes[name] = 1
dfPossiblePlaneTypes = pd.DataFrame.from_dict(possiblePlaneTypes, orient='index', columns=['n_crashes'])
dfPossiblePlaneTypes.loc[dfPossiblePlaneTypes["n_crashes"] > 300] # compagnie validi cessna, boeing, douglas, lockheed
| n_crashes | |
|---|---|
| de | 301 |
| douglas | 1113 |
| boeing | 384 |
| lockheed | 343 |
| dc-3 | 376 |
| cessna | 307 |
dfType["company"] = "ALTRI"
dfType.loc[dfType.index.str.lower().str.contains("cessna"), "company"] = "CESSNA"
dfType.loc[dfType.index.str.lower().str.contains("boeing"), "company"] = "BOEING"
dfType.loc[dfType.index.str.lower().str.contains("douglas"), "company"] = "DOUGLAS"
dfType.loc[dfType.index.str.lower().str.contains("lockheed"), "company"] = "LOCKHEED"
pieDouglasType = px.pie(dfType, values='n_crashes', names='company', title = "Percentuale incidenti dei produttori")
pieDouglasType.update_traces(textposition='inside', textinfo='label+percent')
pieDouglasType.update_layout(showlegend=False,width=700,height=700, font=dict(size= 16))
pieDouglasType.show()
Risposta : Il produttore Douglas è quello che ha la percentuale maggiore di incidenti. Quelli che lo seguono in numero di incidenti sono altri importanti produttori. Quindi sembra più plausibile l’idea che abbiano più incidenti perché sono stati quelli più utilizzati come tipi di velivoli.
Domanda : C'è una differenza della distribuzione nel tempo di incidenti la cui la quantità dei morti rispetto a quelli a bordo è differente? Magari per riuscire a capire se nel tempo i velivoli siano diventati più sicuri, cioè che nonostante l’incidente i passeggeri si possano salvare?
df["aboard_fatalities_type"] = "tutti"
df.loc[df["Fatalities"] < (df["Aboard"]/2), "aboard_fatalities_type"] = "alcuni"
df.loc[df["Fatalities"] >= (df["Aboard"]/2), "aboard_fatalities_type"] = "molti"
df.loc[df["Fatalities"] == 0, "aboard_fatalities_type"] = "nessuno"
boxAboardFatalitiesType = px.box(df,
orientation="h",
y="aboard_fatalities_type",
x="year",
category_orders={"aboard_fatalities_type": ["tutti", "molti", "alcuni", "nessuno"]},
title="Quantità di decessi distributi nel tempo",
height=500,
labels={"aboard_fatalities_type" : "passeggeri morti", "year" : "anno"})
boxAboardFatalitiesType.update_layout(font=dict(size= 16))
boxAboardFatalitiesType.show()
Risposta : Sembra proprio che con l'avanzare del tempo sia diventato più “facile” sopravvivere ad un eventuale incidente. Probabilmente grazie al miglioramento della sicurezza degli aerei anche in caso di incidente. Magari anche grazie ad una migliore formazione dei piloti in casi di emergenza.
Questo codice trova le coordinate per ogni posizione tramite la stringa nella colonna "Location", questo grazie ad un geolocator. Visto che richiede molto tempo viene salvato in un file quindi non è necessario avviarlo se non la prima volta.
from geopy.adapters import AioHTTPAdapter
from geopy.extra.rate_limiter import AsyncRateLimiter
from geopy.geocoders import Nominatim #risorsa online per trovare le posizione in latitudine e longitudine
firstTime = False #Mettere a true per eseguire il codice del geolocator
if firstTime:
dfLatLon=pd.read_csv("data/Airplane_Crashes.csv")
dfLatLon["year"] = pd.DatetimeIndex(dfLatLon["Date"]).year
async with Nominatim(
user_agent="airplane_crashes",
adapter_factory=AioHTTPAdapter,
) as geolocator:
geocode = AsyncRateLimiter(geolocator.geocode, min_delay_seconds=1)
dfLatLon["lat_lon"] = [await geocode(s) for s in dfLatLon["Location"]]
dfLatLon = dfLatLon.loc[dfLatLon["lat_lon"].isnull() == False]
dfLatLon["latitude"] = dfLatLon["lat_lon"].apply(lambda x: x.latitude)
dfLatLon["longitude"] = dfLatLon["lat_lon"].apply(lambda x: x.longitude)
#dfLatLon.to_csv('data/Airplane_Crashes_Lat_Lon.csv') # ! togliere da commento per salvare
Domanda : Come si distribuiscono i vari incidenti per il globo?
dfLatLon=pd.read_csv("data/Airplane_Crashes_Lat_Lon.csv")
mb_crashes = px.scatter_mapbox(dfLatLon, lat="latitude", lon="longitude", title=f"Distribuzione incidenti (posizioni trovate {len(dfLatLon)} su {len(df)})", hover_name='Location', hover_data=["Summary"], zoom=1, height=1000)
mb_crashes.update_layout(mapbox_style="open-street-map")
mb_crashes.show()
Risposta : Gli incidenti sembrano concentrarsi in America e Europa. Però non è possibile dirlo con certezza anche perché, purtroppo, il geolocator ha solo trovato circa 3000 sulle circa 5000 posizione totali.
Domanda : Quindi è necessario ripensare a come fare il grafico?
mb_crashes_anim = px.scatter_mapbox(dfLatLon, lat="latitude", lon="longitude", title="Distribuzione incidenti nel tempo (animato)", hover_name='Location', hover_data=["Summary"], animation_frame="year", animation_group="year", zoom=1, height=1000)
mb_crashes_anim.update_layout(mapbox_style="open-street-map")
mb_crashes_anim.show()
Questo codice funziona molto similmente a quello di prima solo che lo trova per lo stato/nazione definito come parametro più a destra nella colonna “Location”. Questo permette di trovare più posizioni (più di 5000) rispetto a prima che però saranno meno precise.
if firstTime:
df["state"] = df["Location"].str.split(', ').str[-1]
states_df = df.groupby("state").size().reset_index(name='count')
async with Nominatim(
user_agent="airplane_crashes",
adapter_factory=AioHTTPAdapter,
) as geolocator:
geocode = AsyncRateLimiter(geolocator.geocode, min_delay_seconds=1)
states_df["location"] = [await geocode(s) for s in states_df["state"]]
states_df = states_df.loc[states_df["location"].isnull() == False]
states_df["latitude"] = states_df["location"].apply(lambda x: x.latitude)
states_df["longitude"] = states_df["location"].apply(lambda x: x.longitude)
#states_df.to_csv('data/States_Airplane_Crashes.csv') # ! togliere da commento per salvare
states_df=pd.read_csv("data/States_Airplane_Crashes.csv") #togliere commento se si è perso il contenuto del dataframe
mb_crashes = px.scatter_mapbox(states_df, lat="latitude", lon="longitude", title=f"Distribuzione incidenti (posizioni trovate {states_df['count'].sum()} su {len(df)})", size="count", color="count", hover_name='state', zoom=1, height=900, labels={"count" : "numero incidenti"})
mb_crashes.update_layout(mapbox_style="open-street-map")
mb_crashes.show()
Risposta : Si può ancora vedere una concentrazione in America e Europa nonostante il numero maggiore di posizioni (tradotte in grandezze del punto).